﻿using System;
using System.Collections.Generic;
using AntBot.Algorithms;
using AntBot.LRUCache;

namespace AntBot
{
	public class Router
	{
		public const byte WATER = 0;
		public const byte LAND = 1;
		public const byte UNKNOWN = LAND + 100; //free
		public const byte MY_ANT = 2;

		private readonly PathFinderFast pathFinder;
		private readonly Dijkstra dijkstra;

		private readonly LRUCache<IRoute> pathCache;
		private readonly LRUCache<IRoute> foodPathCache;
		private int mapHash;

		private readonly int mCols;
		private readonly int mRows;

		public int DebugAllFoodSearchesCount;
		public int DebugCachedFoodSearchesCount;

		public Router(int cols, int rows)
		{
			pathCache = new LRUCache<IRoute>(cols * rows);
			foodPathCache = new LRUCache<IRoute>(cols * rows);

			mapHash = 0;
			byte[,] grid = new byte[cols, rows];
			for (ushort x = 0; x < cols; x++)
				for (ushort y = 0; y < rows; y++)
					grid[x, y] = UNKNOWN;

			mCols = cols;
			mRows = rows;

			pathFinder = new PathFinderFast(grid)
			{
				Formula = HeuristicFormula.EuclideanNoSQR,
				Diagonals = false,
				HeavyDiagonals = false,
				HeuristicEstimate = 10,
				PunishChangeDirection = false,
				TieBreaker = true,
				SearchLimit = cols * rows,
				DebugProgress = false,
				DebugFoundPath = false
			};

			dijkstra = new Dijkstra(grid)
			{
				GoalsCount = Config.FOOD_COLLECTOR_ANTS_TO_FIND,
				Goal = MY_ANT,
				FromRadius2 = Config.GameSettings.SpawnRadius2,
				SearchRadius2 = Config.FoodCollectorSearchRadius2
			};
		}

		public static List<Location> ReversePath(List<Location> path)
		{
			List<Location> reversedPath = new List<Location>(path);
			reversedPath.Reverse();
			return reversedPath;
		}

		public static Route ReversePath(Route route)
		{
			List<Location> reversedPath = ReversePath(route.Path);
			return new Route(reversedPath, route.MapHash, route.IsShortest);
		}

		private static bool CacheContains(LRUCache<IRoute> cache, Location fromLoc, Location toLoc, int mCols, int mRows, int currentHash)
		{
			fromLoc = Torus.Adjust(fromLoc, mCols, mRows);
			toLoc = Torus.Adjust(toLoc, mCols, mRows);
			Route route = new SimpleRoute(fromLoc, toLoc);
			if (!cache.Contains(route))
				return false;
			route = (Route)cache.Get(route);
			return route != null && route.MapHash == currentHash;
		}

		public bool PathCacheContains(Location fromLoc, Location toLoc)
		{
			return CacheContains(pathCache, fromLoc, toLoc, mCols, mRows, mapHash);
		}

		public bool FoodPathCacheContains(Location antLoc, Location foodLoc)
		{
			return CacheContains(foodPathCache, antLoc, foodLoc, mCols, mRows, mapHash);
		}

		private static int PathCacheCount(LRUCache<IRoute> cache)
		{
			return cache.Count;
		}

		public int PathCacheCount()
		{
			return PathCacheCount(pathCache);
		}

		public int FoodPathCacheCount()
		{
			return PathCacheCount(foodPathCache);
		}

		private Route CacheGet(LRUCache<IRoute> cache, Location fromLoc, Location toLoc, int hash)
		{
			Route route = new SimpleRoute(fromLoc, toLoc);
			return
				CacheContains(cache, fromLoc, toLoc, mCols, mRows, hash) ?
				(Route)cache.Get(route) :
				null;
		}

		public Route PathCacheGet(Location fromLoc, Location toLoc)
		{
			return CacheGet(pathCache, fromLoc, toLoc, mapHash);
		}

		public Route FoodPathCacheGet(Location antLoc, Location foodLoc, int hash)
		{
			return CacheGet(foodPathCache, antLoc, foodLoc, hash);
		}

		private void ClearCache(int hash)
		{
			pathCache.RemoveAll(r => ((Route)r).MapHash == hash);
			foodPathCache.RemoveAll(r => ((Route)r).MapHash == hash);
		}

		public Route SearchPath(Location fromLoc, Location toLoc)
		{
			fromLoc = Torus.Adjust(fromLoc, mCols, mRows);
			toLoc = Torus.Adjust(toLoc, mCols, mRows);
			Route route = PathCacheGet(fromLoc, toLoc);

			if (route != null)
			{
				if (route is NoRoute)
						return route;

				return fromLoc == route.ToLoc ? ReversePath(route) : route;
			}

			//No cached path. Search for it
			List<PathFinderNode> path = pathFinder.FindPath(toLoc, fromLoc);
			route = (path == null) ? Route.NewNoRoute(fromLoc, toLoc) : new Route(path, mapHash, pathFinder.IsShortest);
			pathCache.Add(route);

			return route;
		}

		private static int DVSigned(int value1, int value2, int maxValue)
		{
			//for adjusted values only!
			int dv = value1 - value2;
			int dcAbs = Math.Abs(dv);
			return Math.Min(dcAbs, maxValue - dcAbs) * Math.Sign(dv);
		}

		public Route SearchFoodPathSimple(Location antLoc, Location foodLoc)
		{
			antLoc = Torus.Adjust(antLoc, mCols, mRows);
			foodLoc = Torus.Adjust(foodLoc, mCols, mRows);

			DebugAllFoodSearchesCount++;
			Route route = FoodPathCacheGet(antLoc, foodLoc, 0/*no water*/);
			if (route != null)
			{
				DebugCachedFoodSearchesCount++;
				return route;
			}

			int manh = Torus.ManhattanDistance(antLoc, foodLoc, mCols, mRows);

			List<Location> path = new List<Location>(manh);
			Location currLoc = antLoc;
			path.Add(currLoc);
			while (Torus.Distance2(currLoc, foodLoc, mCols, mRows) > Config.GameSettings.SpawnRadius2)
			{
				int dx = DVSigned(currLoc.X, foodLoc.X, mCols);
				int dy = DVSigned(currLoc.Y, foodLoc.Y, mRows);

				currLoc = (Math.Abs(dx) > Math.Abs(dy)) ? 
					new Location(currLoc.X - Math.Sign(dx), currLoc.Y) :
					new Location(currLoc.X, currLoc.Y - Math.Sign(dy));
				path.Add(currLoc);
			}

			route = new Route(path, antLoc, foodLoc, 0 /*no water*/, false) {IsPathToFood = true};
			foodPathCache.Add(route);

			return route;
		}

		public Route SearchFoodPath(Location antLoc, Location foodLoc)
		{
			if (Config.FoodCollectorUseSimplePathFinding)
				return SearchFoodPathSimple(antLoc, foodLoc);

			antLoc = Torus.Adjust(antLoc, mCols, mRows);
			foodLoc = Torus.Adjust(foodLoc, mCols, mRows);

			DebugAllFoodSearchesCount++;
			Route route = FoodPathCacheGet(antLoc, foodLoc, mapHash);
			if (route != null)
			{
				DebugCachedFoodSearchesCount++;
				return route;
			}

			//No cached foodpath. Search for it
			dijkstra.DoWork(foodLoc);
			List<Location> path = dijkstra.GetReversedPath(antLoc);
			route = (path == null) ?
						Route.NewNoRoute(antLoc, foodLoc) :
						new Route(path, antLoc, foodLoc, mapHash, true);//TODO why isShortest?

			route.IsPathToFood = true;
			foodPathCache.Add(route);

			return route;
		}

		public void SetWater(int col, int row)
		{
			pathFinder.ChangeSquareValue((ushort)col, (ushort)row, WATER);
			dijkstra.ChangeSquareValue((ushort)col, (ushort)row, WATER);
			mapHash++;
		}

		public void DeleteWater(int col, int row)
		{
			pathFinder.ChangeSquareValue((ushort)col, (ushort)row, LAND);
			dijkstra.ChangeSquareValue((ushort)col, (ushort)row, LAND);
			ClearCache(mapHash);
			mapHash--;
		}

		public static List<Location> PathFinderNodeListToLocationList(List<PathFinderNode> pathNode)
		{
			if (pathNode == null) return null;

			List<Location> pathLoc = new List<Location>(pathNode.Count);
			foreach (PathFinderNode pathFinderNode in pathNode)
			{
				pathLoc.Add(new Location(pathFinderNode.X, pathFinderNode.Y));
			}
			return pathLoc;
		}
	}
}